package com.jplus.core.util;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

/**
 * Convert object to json string.
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class JsonUtil {

    private static int convertDepth = 15;
    private static String timestampPattern = "yyyy-MM-dd HH:mm:ss";
    private static String datePattern = "yyyy-MM-dd";

    public static void setConvertDepth(int convertDepth) {
	if (convertDepth < 2)
	    throw new IllegalArgumentException("convert depth can not less than 2.");
	JsonUtil.convertDepth = convertDepth;
    }

    public static void setTimestampPattern(String timestampPattern) {
	if (timestampPattern == null || "".equals(timestampPattern.trim()))
	    throw new IllegalArgumentException("timestampPattern can not be blank.");

	JsonUtil.timestampPattern = timestampPattern;
    }

    public static void setDatePattern(String datePattern) {
	if (datePattern == null || "".equals(datePattern.trim()))
	    throw new IllegalArgumentException("datePattern can not be blank.");
	JsonUtil.datePattern = datePattern;
    }

    private static String mapToJson(Map map, int depth) {
	if (map == null)
	    return "null";

	StringBuilder sb = new StringBuilder();
	boolean first = true;
	Iterator iter = map.entrySet().iterator();

	sb.append('{');
	while (iter.hasNext()) {
	    if (first)
		first = false;
	    else
		sb.append(',');

	    Map.Entry entry = (Map.Entry) iter.next();
	    toKeyValue(String.valueOf(entry.getKey()), entry.getValue(), sb, depth);
	}
	sb.append('}');
	return sb.toString();
    }

    private static String toKeyValue(String key, Object value, StringBuilder sb, int depth) {
	sb.append('\"');
	if (key == null)
	    sb.append("null");
	else
	    escape(key, sb);
	sb.append('\"').append(':');
	sb.append(toJson(value, depth));
	return sb.toString();
    }

    private static String listToJson(List list, int depth) {
	if (list == null)
	    return "null";
	boolean first = true;
	StringBuilder sb = new StringBuilder();
	Iterator iter = list.iterator();
	sb.append('[');
	while (iter.hasNext()) {
	    if (first)
		first = false;
	    else
		sb.append(',');
	    Object value = iter.next();
	    if (value == null) {
		sb.append("null");
		continue;
	    }
	    sb.append(toJson(value, depth));
	}
	sb.append(']');
	return sb.toString();
    }

    /**
     * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
     * (U+0000 through U+001F).
     */
    private static String escape(String s) {
	if (s == null)
	    return null;
	StringBuilder sb = new StringBuilder();
	escape(s, sb);
	return sb.toString();
    }

    private static void escape(String s, StringBuilder sb) {
	for (int i = 0; i < s.length(); i++) {
	    char ch = s.charAt(i);
	    switch (ch) {
	    case '"':
		sb.append("\\\"");
		break;
	    case '\\':
		sb.append("\\\\");
		break;
	    case '\b':
		sb.append("\\b");
		break;
	    case '\f':
		sb.append("\\f");
		break;
	    case '\n':
		sb.append("\\n");
		break;
	    case '\r':
		sb.append("\\r");
		break;
	    case '\t':
		sb.append("\\t");
		break;
	    case '/':
		sb.append("\\/");
		break;
	    default:
		if ((ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) {
		    String str = Integer.toHexString(ch);
		    sb.append("\\u");
		    for (int k = 0; k < 4 - str.length(); k++) {
			sb.append('0');
		    }
		    sb.append(str.toUpperCase());
		} else {
		    sb.append(ch);
		}
	    }
	}
    }

    public static String toJson(Object value) {
	return toJson(value, convertDepth);
    }

    public static String toJson(Object value, int depth) {
	if (value == null || (depth--) < 0)
	    return "null";

	if (value instanceof String)
	    return "\"" + escape((String) value) + "\"";

	if (value instanceof Double) {
	    if (((Double) value).isInfinite() || ((Double) value).isNaN())
		return "null";
	    else
		return value.toString();
	}
	if (value instanceof Float) {
	    if (((Float) value).isInfinite() || ((Float) value).isNaN())
		return "null";
	    else
		return value.toString();
	}
	if (value instanceof Number)
	    return value.toString();
	if (value instanceof Boolean)
	    return value.toString();
	if (value instanceof java.util.Date) {
	    if (value instanceof java.sql.Timestamp)
		return "\"" + new SimpleDateFormat(timestampPattern).format(value) + "\"";
	    if (value instanceof java.sql.Time)
		return "\"" + value.toString() + "\"";
	    return "\"" + new SimpleDateFormat(datePattern).format(value) + "\"";
	}
	if (value instanceof Map) {
	    return mapToJson((Map) value, depth);
	}
	if (value instanceof List) {
	    return listToJson((List) value, depth);
	}
	String result = otherToJson(value, depth);
	if (result != null)
	    return result;
	// 类型无法处理时当作字符串处理,否则ajax调用返回时js无法解析
	// return value.toString();
	return "\"" + escape(value.toString()) + "\"";
    }

    private static String otherToJson(Object value, int depth) {
	if (value instanceof Character) {
	    return "\"" + escape(value.toString()) + "\"";
	}
	if (value instanceof Object[]) {
	    Object[] arr = (Object[]) value;
	    List list = new ArrayList(arr.length);
	    for (int i = 0; i < arr.length; i++)
		list.add(arr[i]);
	    return listToJson(list, depth);
	}
	if (value instanceof Enum) {
	    return "\"" + ((Enum) value).toString() + "\"";
	}
	return beanToJson(value, depth);
    }

    private static String beanToJson(Object model, int depth) {
	Map map = new HashMap();
	Method[] methods = model.getClass().getMethods();
	for (Method m : methods) {
	    String methodName = m.getName();
	    int indexOfGet = methodName.indexOf("get");
	    if (indexOfGet == 0 && methodName.length() > 3) { // Only getter
		String attrName = methodName.substring(3);
		if (!attrName.equals("Class")) { // Ignore Object.getClass()
		    Class<?>[] types = m.getParameterTypes();
		    if (types.length == 0) {
			try {
			    Object value = m.invoke(model);
			    map.put(FormatUtil.firstCharToLowerCase(attrName), value);
			} catch (Exception e) {
			    throw new RuntimeException(e.getMessage(), e);
			}
		    }
		}
	    } else {
		int indexOfIs = methodName.indexOf("is");
		if (indexOfIs == 0 && methodName.length() > 2) {
		    String attrName = methodName.substring(2);
		    Class<?>[] types = m.getParameterTypes();
		    if (types.length == 0) {
			try {
			    Object value = m.invoke(model);
			    map.put(FormatUtil.firstCharToLowerCase(attrName), value);
			} catch (Exception e) {
			    throw new RuntimeException(e.getMessage(), e);
			}
		    }
		}
	    }
	}
	return mapToJson(map, depth);
    }

    // ========================================================
    public static Object parse(String jsonStr) {
	int len = jsonStr.length();
	String sp = "|";//
	Boolean cs = Boolean.FALSE, ignore = Boolean.FALSE;
	Stack<Character> s1 = new Stack<Character>();// 符号
	Stack<Object> s2 = new Stack<Object>();// 值
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < len; i++) {
	    char c = jsonStr.charAt(i);
	    switch (c) {
	    case '{':
	    case '[':
		s1.add(c);
		s2.add(sp);
		sb.setLength(0);
		break;
	    case ']':
	    case '}':
		if (s1.peek() != '"') {
		    cs = addCheck(sb, cs, s2);
		    char ls2 = s1.pop();
		    if ((c == '}' && ls2 != '{') || (c == ']' && ls2 != '['))
			throw new RuntimeException("JSON 格式错误 ");
		    Object obj = null;
		    // =================
		    if (c == '}') {// map
			Map map = new HashMap<>();
			while (!sp.equals(s2.peek())) {
			    Object v = s2.pop();
			    if ("".equals(v) && sp.equals(s2.peek())) // 验证空对象
				continue;
			    Object k = s2.pop();
			    map.put(k, v);
			}
			obj = map;
		    }
		    if (c == ']') {// list
			List list = new ArrayList<>();
			while (!sp.equals(s2.peek())) {
			    list.add(s2.pop());
			}
			obj = list;
		    }
		    // =================
		    s2.pop();
		    s2.add(obj);
		    ignore = Boolean.TRUE;
		} else {
		    sb.append(c);
		}
		break;
	    case '"':
		if (jsonStr.charAt(i - 1) != '\\')
		    if (s1.peek() == '"') {
			s1.pop();
			cs = Boolean.TRUE;
		    } else
			s1.add('"');
		break;
	    case ':':
	    case ',':
		if (s1.peek() != '"') {
		    if (ignore && c == ',') {
			ignore = Boolean.FALSE;
			continue;
		    }
		    cs = addCheck(sb, cs, s2);
		} else {
		    sb.append(c);
		}
		break;
	    default:
		sb.append(c);
		break;
	    }
	}
	return s2.pop();
    }

    private static boolean addCheck(StringBuffer sb, Boolean cs, Stack<Object> s2) {
	Object val = sb.toString().trim();
	if (!cs) {
	    if (FormatUtil.isNumber(val))
		val = FormatUtil.toLong(val);
	    else if (FormatUtil.isDecimal(val))
		val = FormatUtil.toDouble(val);
	    else if (((String) val).equalsIgnoreCase("null"))
		val = null;
	    else if (((String) val).equalsIgnoreCase("")) {
		sb.setLength(0);
		return false;
	    } else
		val = FormatUtil.toBoolean(val);
	}
	s2.add(val);
	sb.setLength(0);
	return false;
    }

    public static <T> Object parse(String jsonStr, Class<T> clazz) throws Exception {
	Object jsonObj = parse(jsonStr);
	return parseBean(jsonObj, clazz);
    }

    private static <T> Object parseBean(Object json, Class<T> clazz) throws Exception {
	try {
	    if (json instanceof List) {
		List<T> list = new ArrayList<T>();
		for (Object obj : (List) json) {
		    if (!obj.equals("")) {
			list.add((T) parseBean(obj, clazz));
		    }
		}
		return list;
	    } else if (json instanceof Map) {
		Object obj = null;
		obj = clazz.newInstance();
		Set<Map.Entry<?, ?>> setMap = ((Map) json).entrySet();
		for (Map.Entry<?, ?> en : setMap) {
		    String key = FormatUtil.toString(en.getKey());
		    Object value = en.getValue();
		    Method m = null;
		    for (Method mt : clazz.getMethods()) {
			if (mt.getName().equals("set" + FormatUtil.firstCharToUpperCase(key)))
			    m = mt;
		    }
		    if (m != null) {
			Class cla = m.getParameterTypes()[0];
			Object args = null;
			if (value != null) {
			    if (cla.isAssignableFrom(Map.class)) {
				cla = (Class) ((ParameterizedType) (m.getGenericParameterTypes()[0])).getActualTypeArguments()[1];
				Map map = new HashMap<>();
				Set<Map.Entry<?, ?>> setMap2 = ((Map) value).entrySet();
				for (Map.Entry<?, ?> en2 : setMap2) {
				    map.put(parseBean(en2.getKey(), String.class), parseBean(en2.getValue(), cla));
				}
				args = map;
			    } else {
				if (cla.isAssignableFrom(List.class))
				    cla = (Class) ((ParameterizedType) (m.getGenericParameterTypes()[0])).getActualTypeArguments()[0];
				args = parseBean(value, cla);
			    }
			}
			m.invoke(obj, args);
		    }
		}
		return obj;
	    } else if (json instanceof String[]) {
		String[] strs = (String[]) json;
		Object[] objs = new Object[strs.length];
		for (int i = 0; i < strs.length; i++) {
		    objs[i] = parse(strs[i], clazz);
		}
		return objs;
	    } else {
		if (ClassUtil.isString(clazz)) {
		    return FormatUtil.toString(json);
		} else if (ClassUtil.isDouble(clazz)) {
		    return FormatUtil.toDouble(json);
		} else if (ClassUtil.isInt(clazz)) {
		    return FormatUtil.toInt(json);
		} else if (ClassUtil.isLong(clazz)) {
		    return FormatUtil.toLong(json);
		} else if (ClassUtil.isDecimal(clazz)) {
		    return FormatUtil.toDecimal(json);
		} else if (ClassUtil.isBool(clazz)) {
		    return FormatUtil.toBoolean(json);
		} else {
		    throw new Exception("未知文件类型转换：value=" + json + ",type=" + clazz);
		}
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
	return null;
    }

}
